/*******************************************************************************
 * Copyright (c) 2012 March and Conquest.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mrusanov.towers.main;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Date;

import mrusanov.towers.cards.AbstractCard;
import mrusanov.towers.configuration.GameConfigurationManager;
import mrusanov.towers.configuration.GamefieldConfiguration;
import mrusanov.towers.main.sounds.Event;
import mrusanov.towers.main.sounds.MusicPlayer;
import mrusanov.towers.main.ui.MainForm;
import mrusanov.towers.net.ClientCommunicatorThread;
import mrusanov.towers.net.CommunicatorTask;
import mrusanov.towers.net.CommunicatorThread;
import mrusanov.towers.net.EndCommunicationTask;
import mrusanov.towers.net.ReceiveTurnInfoCommunicationTask;
import mrusanov.towers.net.SendPlayerNameCommunicationTask;
import mrusanov.towers.net.SendTurnInfoCommunicationTask;
import mrusanov.towers.net.ServerCommunicatorThread;
import mrusanov.towers.net.StartClientGameCommunicatorTask;
import mrusanov.towers.net.StartHostedGameCommunicatorTask;
import mrusanov.towers.net.StartingPlayer;
import mrusanov.towers.net.TurnInfo;
import mrusanov.towers.net.TurnInfo.TurnDecision;
import mrusanov.towers.net.WriteNameCommunicationTask;
import mrusanov.towers.players.Player;
import mrusanov.towers.stats.GameStatistics;
import mrusanov.towers.stats.GameStatisticsManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DesktopGame {

	private static final Logger LOG = LoggerFactory.getLogger(DesktopGame.class);

	private Game game;
	private CommunicatorThread communicatorThread;

	private String enemyName;
	private String currentPlayerName;
	private MainForm mainForm;

	private Thread thread;

	public DesktopGame() {
		LOG.debug("Desktop game constructor invoked");
		currentPlayerName = GameConfigurationManager.getConfiguration().getPlayerName();
	}

	public void hostGame(int port) {
		LOG.info("Trying to host game using port [" + port + "]");
		communicatorThread = new ServerCommunicatorThread(this, port);
		startCommunicator();

		// order is important
		addTask(new SendPlayerNameCommunicationTask());
		addTask(new WriteNameCommunicationTask(currentPlayerName));

		GamefieldConfiguration configuration = GameConfigurationManager.getConfiguration().getGamefieldConfiguration();

		addTask(new StartHostedGameCommunicatorTask(StartingPlayer.getRandomStartingPlayer(), configuration));

		mainForm.hostingStarted();
	}

	public void setMainForm(MainForm mainForm) {
		this.mainForm = mainForm;
	}

	public Player getAnotherPlayer(Player current) {
		return game.getAnotherPlayer(current);
	}

	public void joinGame(String host, int port) {
		LOG.info("Trying to join game using host [" + host + "] port [" + port + "]");

		communicatorThread = new ClientCommunicatorThread(this, host, port);
		startCommunicator();

		// order is important
		addTask(new WriteNameCommunicationTask(currentPlayerName));
		addTask(new SendPlayerNameCommunicationTask());

		addTask(new StartClientGameCommunicatorTask());
	}

	private void startCommunicator() {
		thread = new Thread(communicatorThread);
		thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {

			@Override
			public void uncaughtException(Thread t, Throwable e) {
				LOG.error(e.getMessage(), e);
			}
		});
		thread.start();
	}

	public Iterable<AbstractCard> getThisPlayerCards() {
		return game.getThisPlayer().getHand();
	}

	public Player getThisPlayer() {
		return game.getThisPlayer();
	}

	public Player getEnemyPlayer() {
		return game.getEnemyPlayer();
	}

	public String getEnemyName() {
		return enemyName;
	}

	public void setEnemyName(String enemyName) {
		this.enemyName = enemyName;
	}

	public Player getCurrentMovingPlayer() {
		return game.getCurrentMovingPlayer();
	}

	public void startGame(StartingPlayer startingPlayer, GamefieldConfiguration gamefieldConfiguration) {
		LOG.info("Starting game with starting player " + startingPlayer);
		game = new Game(currentPlayerName, enemyName, gamefieldConfiguration);
		Player host, client;

		if (communicatorThread.isServer()) {
			host = game.getThisPlayer();
			client = game.getEnemyPlayer();
		} else {
			host = game.getEnemyPlayer();
			client = game.getThisPlayer();
		}

		Player firstMoving = null;
		if (startingPlayer == StartingPlayer.CLIENT) {
			firstMoving = client;
		} else {
			firstMoving = host;
		}

		game.initFirstMovingPlayer(firstMoving);
		if (firstMoving.equals(game.getEnemyPlayer())) {
			addTask(new ReceiveTurnInfoCommunicationTask());
		}

		mainForm.startGame(gamefieldConfiguration);
		MusicPlayer.playSound(Event.JOIN_GAME);
	}

	public String getCurrentPlayerName() {
		return currentPlayerName;
	}

	public void setCurrentPlayerName(String currentPlayerName) {
		this.currentPlayerName = currentPlayerName;
	}

	public void receiveEnemyPlayerTurn(TurnInfo turnInfo) {
		LOG.info("Received enemy turn info: " + turnInfo);
		applyTurnInfo(turnInfo);
		mainForm.new UpdateUIAfterMove(turnInfo).executeTask();

		if (game.getGameResult() == GameResult.GAME_IN_PROGRESS
				&& game.getCurrentMovingPlayer().equals(game.getEnemyPlayer())) {
			addTask(new ReceiveTurnInfoCommunicationTask());
		}
	}

	public void receiveThisPlayerTurn(TurnInfo turnInfo) {
		LOG.info("Received this player turn info: " + turnInfo);
		applyTurnInfo(turnInfo);

		SendTurnInfoCommunicationTask task = new SendTurnInfoCommunicationTask(turnInfo);
		addTask(task);

		if (game.getGameResult() == GameResult.GAME_IN_PROGRESS
				&& game.getCurrentMovingPlayer().equals(game.getEnemyPlayer())) {
			addTask(new ReceiveTurnInfoCommunicationTask());
		}
	}

	public void restartGameAfterCompletion() {
		LOG.info("Restarting game");
		if (communicatorThread.isServer()) {
			GamefieldConfiguration configuration = GameConfigurationManager.getConfiguration()
					.getGamefieldConfiguration();
			addTask(new StartHostedGameCommunicatorTask(StartingPlayer.getRandomStartingPlayer(), configuration));
		} else {
			addTask(new StartClientGameCommunicatorTask());
		}

	}

	public GameResult getGameResult() {
		return game.getGameResult();
	}

	private void applyTurnInfo(TurnInfo info) {
		if (info.getTurnDecision() == TurnDecision.PLAY_CARD) {
			TurnStats turnStats = new TurnStats(getThisPlayer(), getEnemyPlayer());
			turnStats.rememberBeforeState();
			game.playCard(info.getCard());
			turnStats.rememberAfterState();
			MusicPlayer.playSounds(turnStats.calculateEvents());
		} else {
			game.throwCard(info.getCard());
			MusicPlayer.playSound(Event.TURN_END);
		}

		if (game.getGameResult() != GameResult.GAME_IN_PROGRESS
				&& game.getGameResult() != GameResult.BOTH_PLAYERS_DEFEATED) {
			GameStatisticsManager.addGameResult(buildGameStatistics());
		}
	}

	public boolean isThisPlayerTurn() {
		return game.getCurrentMovingPlayer().equals(game.getThisPlayer());
	}

	public void stopHosting() {
		LOG.info("Stop hosting invoked");
		if (communicatorThread != null) {
			communicatorThread.dispose();
		}

		thread = null;
		communicatorThread = null;
		game = null;
		enemyName = null;
	}

	public void stopGame() {
		addTask(new EndCommunicationTask());
	}

	public void hostingError() {
		mainForm.hostingError();
		stopHosting();
	}

	public void joiningError() {
		mainForm.joiningError();
		stopHosting();
	}

	public void connectionError() {
		mainForm.connectionFailed();
		stopHosting();
	}

	public void unknownError(Exception ex) {
		mainForm.unknownError(ex);
		stopHosting();
	}

	private void addTask(CommunicatorTask task) {
		LOG.debug("Adding new communicator task: " + task);
		communicatorThread.addCommunicatorTask(task);
	}

	public Player getPreviouslyMovedPlayer(TurnInfo info) {
		return game.getPreviouslyMovedPlayer(info);
	}

	private GameStatistics buildGameStatistics() {
		GameStatistics stats = new GameStatistics();
		stats.setDate(new Date());

		GameResult gameResult = game.getGameResult();
		Player winner;
		Player loser;
		if (gameResult == GameResult.ENEMY_PLAYER_RAISED_TOWER || gameResult == GameResult.THIS_PLAYER_DEFEATED) {
			winner = game.getEnemyPlayer();
			loser = game.getThisPlayer();
		} else {
			loser = game.getEnemyPlayer();
			winner = game.getThisPlayer();
		}

		stats.setLoser(loser.getName());
		stats.setWinner(winner.getName());
		stats.setLosersTowerHeight(loser.getTower().getTowerHeight());
		stats.setWinnersTowerHeight(winner.getTower().getTowerHeight());
		stats.setWinnersWallHeight(winner.getTower().getWallHeight());
		stats.setLosersWallHeight(loser.getTower().getWallHeight());
		stats.setTurns(game.getTurnNumber());

		return stats;
	}
}
